package cn.zswltech.gruul.biz.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;

import cn.zswltech.gruul.biz.service.OrgService;
import cn.zswltech.gruul.biz.service.SystemConfigService;
import cn.zswltech.gruul.biz.service.UserService;
import cn.zswltech.gruul.biz.support.impl.IServiceImpl;
import cn.zswltech.gruul.common.constant.RedisKeyConstant;
import cn.zswltech.gruul.common.constant.SystemConfigConstant;
import cn.zswltech.gruul.common.constant.UserConstants;
import cn.zswltech.gruul.common.dal.dao.*;
import cn.zswltech.gruul.common.dal.vo.*;
import cn.zswltech.gruul.common.entity.*;
import cn.zswltech.gruul.common.enums.OrgTypeEnum;
import cn.zswltech.gruul.common.enums.SystemCodeEnum;
import cn.zswltech.gruul.common.result.MSG;
import cn.zswltech.gruul.common.result.Response;
import cn.zswltech.gruul.common.util.*;
import cn.zswltech.gruul.common.dal.query.UserQuery;
import cn.zswltech.gruul.common.dal.tkmybatis.Page;
import cn.zswltech.gruul.web.api.util.*;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.RowBounds;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.util.Sqls;

import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static cn.hutool.core.bean.BeanUtil.copyToList;


@Service("userServiceAPI")
@Slf4j
public class UserServiceImpl extends IServiceImpl<UserDO> implements UserService {
    @Resource
    private UserDOMapper userDOMapper;
    @Resource
    private RoleDOMapper roleDOMapper;
    @Resource
    private OrgDOMapper orgDOMapper;
    @Resource
    private OrgService orgService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private SystemConfigService systemConfigService;

    private static final Pattern numericPattern = Pattern.compile("[0-9]*");

    @Value("${system.token.expiration}")
    public long tokenExpiration;

    @Resource
    private HttpServletRequest req;

    @Resource
    private UserOrgRoleDOMapper userOrgRoleDOMapper;

    @Resource
    private DictionaryDOMapper dictionaryDOMapper;

    private Map<Long, OrgDO> orgDOMap;

    @Resource
    private UserPswdMapper userPswdMapper;

    @Resource
    private AdministratorDoMapper administratorDoMapper;

    @PostConstruct
    public void init() {
        orgDOMap = orgDOMapper.selectAll().stream().collect(Collectors.toMap(OrgDO::getId, Function.identity()));

    }

    @Override
    public UserDO queryByAccount(String account) {
        try {
            UserDO query = new UserDO();
            query.setAccount(account);
            return userDOMapper.selectOne(query);
        } catch (Exception e) {
            log.error("User query error may account error", e);
        }
        return null;
    }

    @Override
    public List<UserDO> jobUsers(Long orgId, String jobCode) {
        List<Long> userIds = userOrgRoleDOMapper.selectUserIdByOrgIdAndRoleId(orgId, null);
        if (CollectionUtils.isEmpty(userIds)) {
            return null;
        }
        List<UserDO> user = userDOMapper.queryLikeJobs(jobCode, userIds);
        return user;

    }

    @Override
    public List<UserDO> getUsersByjobcod(String jobCode) {
        List<UserDO> userDOS = userDOMapper.queryByJob(jobCode);
        return userDOS;
    }


    @Override
    public Response logout() {
        Response response = new Response();
        try {
            AccountVO accountVO = AccountUtil.getLoginInfo();
            if (accountVO == null) {
                response.setMessage(MSG.req_error_sign_not_pass);
                return response;
            }
//            UserDO user = new UserDO();
//            user.setTokenMD5("");
//            Example example = new Example(UserDO.class);
//            Example.Criteria criteria = example.createCriteria();
//            criteria.andEqualTo("account", accountVO.getAccount());
//            userDOMapper.updateByExampleSelective(user, example);
            return Response.success(null);
        } catch (Exception e) {
            log.error("User logout error", e);
        }
        return response;
    }

    @Override
    public void createAuthCode(HttpServletResponse response, Map<String, Object> returnMap) {
        ValidateCodeUtil.Validate validate = ValidateCodeUtil.getRandomCode();
        returnMap.put("authCode", validate.getBase64Str());
        returnMap.put("authCodeAes", MD5Util.MD5(validate.getValue().toUpperCase()));
    }


    @Override
    public Response<Map<String, Object>> checkToken() {
        Response<Map<String, Object>> response = new Response<Map<String, Object>>();
        try {
            AccountVO accountVO = AccountUtil.getLoginInfo();
            if (accountVO == null) {
                response.setMessage(MSG.req_error_sign_not_pass);
                return response;
            }
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("account", accountVO.getAccount());
            map.put("expiration", accountVO.getExpiration());
            return Response.success(map);
        } catch (Exception e) {
            log.error("API checkToken error", e);
        }
        return response;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response resetPwd(Long id, HttpServletResponse httpServletResponse) {
        Response response = new Response();
        try {
            UserDO userIndb = userDOMapper.selectByPrimaryKey(id);
            if (userIndb == null) {
                response.setMessage(MSG.req_error_param_invalid, "id");
                return response;
            }
            userIndb.setPwd(AccountUtil.genPwd(UserConstants.DEFAULT_PWD, userIndb.getSalt()));
            String jwtToken = JwtUtils.createJWT(id, userIndb.getPwd(), userIndb.getAccount(), tokenExpiration);
            userIndb.setTokenMD5(MD5Util.MD5(jwtToken));
            userIndb.setFirstLogin("0");
            userIndb.setTryTime(0); // 密码试错次数重置，不然重置了密码也无法登录
            //需要重置密码 设置为前一天，代表过期。
            userIndb.setUpdatePwdTime(DateUtils.addDay(new Date(), -1));
            updateUser(userIndb, null, false);

            // 当前用户,设置cookie
            Map<String, Object> rsp = new HashMap<>();
            AccountVO accountVO = AccountUtil.getLoginInfo();
            if (accountVO.getAccount().equals(userIndb.getAccount())) {
                //当前用户重置自己的密码，则token发生变化，crsf也发生变化
//                CookieUtils.setWlToken(httpServletResponse, jwtToken);
                String csrfToken = TokenUtil.encryptAsString(TokenUtil.getCRSFToken(jwtToken), TokenUtil.CSRF_TOKEN_PUBLICKEY);
                rsp.put("csrfToken", csrfToken);
                rsp.put(CookieUtils.WL_TOKEN, jwtToken);
                return Response.success(rsp);
            }
            //当前用户重置别人的密码，则token保持不变，crsf也保持不变
            String oldWlToken = AccountUtil.getCurrentUserToken();
            String csrfToken = TokenUtil.encryptAsString(TokenUtil.getCRSFToken(oldWlToken), TokenUtil.CSRF_TOKEN_PUBLICKEY);
            rsp.put("csrfToken", csrfToken);
            return Response.success(rsp);
        } catch (Exception e) {
            log.error("API changePwd error", e);
            response.setMessage(MSG.req_error_modify_password_failed);
        }
        return response;
    }


    @Override
    public Response changePwdAfterLogin(String encryptNewPwd) {
        Response response = new Response();
        try {

            String newPwd = TokenUtil.decrypt(encryptNewPwd, TokenUtil.PWD_PRIVATEKEY);

            if (StringUtils.isBlank(newPwd)) {
                response.setMessage(MSG.req_error_param_null, "newPwd");
                return response;
            }

            if (!AccountUtil.validatePwd(newPwd, this.getPasswordRegexConfig())) {
                //密码必须同时包含8~18位数字、大小写字母及特殊字符
                response.setMessage(MSG.req_error_param_password_not_strong, newPwd);
                return response;
            }

            AccountVO accountVO = AccountUtil.getLoginInfo();
            if (accountVO == null) {
                response.setMessage(MSG.req_error_sign_not_pass);
                return response;
            }
            UserDO userQuery = new UserDO();
            userQuery.setAccount(accountVO.getAccount());
            UserDO userIndb = userDOMapper.selectOne(userQuery);
            if (userIndb == null) {
                response.setMessage(MSG.req_error_user_not_exist);
                return response;
            }

            Date updatePwdTime = userIndb.getUpdatePwdTime();
            if (updatePwdTime != null && !updatePwdTime.before(new Date())) {
                // 不满足强制修改密码的触发条件，不能调用当前接口修改密码
                log.warn("用户未无需强制修改密码，不能调用无原密码修改密码接口: user.name={}", userIndb.getUserName());
                response.setMessage(MSG.req_error_modify_password_failed);
                return response;
            }

            String pwdEntry = AccountUtil.genPwd(newPwd, userIndb.getSalt());
            if (pwdEntry.equalsIgnoreCase(userIndb.getPwd())) {
                response.setMessage(MSG.req_error_new_and_old_password_is_equal);
                return response;
            }
            userIndb.setPwd(pwdEntry);
            String jwtToken = JwtUtils.createJWT(userIndb.getId(), userIndb.getPwd(), accountVO.getAccount(), tokenExpiration);
            userIndb.setTokenMD5(MD5Util.MD5(jwtToken));
            // 更新密码过期时间
            Response<SystemConfigDO> result = systemConfigService.getConfig(SystemConfigConstant.PASSWORD_UPDATE_TIME);
            SystemConfigDO systemConfigDO = result.getData();
            if (systemConfigDO != null) {
                String time = systemConfigDO.getConfigValue();
                if (StringUtils.isNotEmpty(time)) {
                    if (isNumeric(time)) {
                        userIndb.setUpdatePwdTime(DateUtils.addDay(new Date(), Integer.parseInt(time)));
                    } else {
                        userIndb.setUpdatePwdTime(DateUtils.addDay(new Date(), 1));
                    }
                }
            }
            userIndb.setFirstLogin("1");
            updateNotNull(userIndb);
            // 设置cookie
            Map<String, Object> rsp = new HashMap<>();
//            CookieUtils.setWlToken(httpServletResponse, jwtToken);
            rsp.put(CookieUtils.WL_TOKEN, jwtToken);
            String csrfToken = TokenUtil.encryptAsString(TokenUtil.getCRSFToken(jwtToken), TokenUtil.CSRF_TOKEN_PUBLICKEY);
            rsp.put("csrfToken", csrfToken);

            // FIXME 修改成功时记录密码明文，这个功能后续要删除，暂时使用
            this.recordPswdAfterUpdate(userIndb, newPwd);

            return Response.success(rsp);
        } catch (Exception e) {
            log.error("API changePwd error", e);
            response.setMessage(MSG.req_error_modify_password_failed);
        }
        return response;
    }

    private String getPasswordRegexConfig() {
        String passwordRegex;
        Response<SystemConfigDO> passwordRegexConfig = systemConfigService.getConfig(SystemConfigConstant.PASSWORD_REGEX);
        if (passwordRegexConfig != null && passwordRegexConfig.isSuccess() && passwordRegexConfig.getData() != null && StringUtils.isNotBlank(passwordRegexConfig.getData().getConfigValue())) {
            passwordRegex = passwordRegexConfig.getData().getConfigValue();
        } else {
            passwordRegex = null;
        }
        return passwordRegex;
    }

    @Override
    public Response innerChangePassword(String account, String newPwd) {
        Response response = new Response();
        try {
            if (StringUtils.isBlank(newPwd)) {
                response.setMessage(MSG.req_error_param_null, "newPwd");
                return response;
            }
            UserDO userQuery = new UserDO();
            userQuery.setAccount(account);
            UserDO userIndb = userDOMapper.selectOne(userQuery);
            if (userIndb == null) {
                response.setMessage(MSG.req_error_user_not_exist);
                return response;
            }

            String pwdEntry = AccountUtil.genPwd(newPwd, userIndb.getSalt());
            if (pwdEntry.equalsIgnoreCase(userIndb.getPwd())) {
                response.setMessage(MSG.req_error_new_and_old_password_is_equal);
                return response;
            }
            userIndb.setPwd(pwdEntry);
            String jwtToken = JwtUtils.createJWT(userIndb.getId(), userIndb.getPwd(), account, tokenExpiration);
            userIndb.setTokenMD5(MD5Util.MD5(jwtToken));
            updateNotNull(userIndb);
            // 设置cookie
            Map<String, Object> rsp = new HashMap<>();
//            CookieUtils.setWlToken(httpServletResponse, jwtToken);
            rsp.put(CookieUtils.WL_TOKEN, jwtToken);
            String csrfToken = TokenUtil.encryptAsString(TokenUtil.getCRSFToken(jwtToken), TokenUtil.CSRF_TOKEN_PUBLICKEY);
            rsp.put("csrfToken", csrfToken);
            return Response.success(rsp);
        } catch (Exception e) {
            log.error("API changePwd error", e);
            response.setMessage(MSG.req_error_modify_password_failed);
        }
        return response;
    }

    @Override
    public Response<List<GruulDictionaryDTO>> accountUsernameMappingList(String orgCode) {
        StopWatch stw = new StopWatch("accountUsernameMappingList");
        List<UserDO> userDOS;
        if (StringUtils.isBlank(orgCode)) {
            Example example = new Example(UserDO.class);
            example.createCriteria().andIsNotNull("account");
            example.selectProperties("account", "userName");
            stw.start("全量用户账号、中文名查询");
            userDOS = userDOMapper.selectByExample(example);
            stw.stop();
        } else {
            stw.start("机构层级递归查询用户账号、中文名");
            userDOS = this.getUserByParentOrgCode(orgCode);
            stw.stop();
        }
        if (CollectionUtils.isEmpty(userDOS)) {
            return Response.success(Collections.emptyList());
        }
        stw.start("用户对象转map");
        Response<List<GruulDictionaryDTO>> dtoList = Response.success(userDOS.stream().map(e -> new GruulDictionaryDTO(e.getAccount(), e.getUserName())).collect(Collectors.toList()));
        stw.stop();
        log.info("accountUsernameMappingList耗时情况:{}", stw.prettyPrint(TimeUnit.MILLISECONDS));
        return dtoList;
    }

    @Override
    public void pushUser2Redis() {
        List<UserDO> userDOList = userDOMapper.selectAll();
        if (ObjectUtil.isEmpty(userDOList)) {
            return;
        }

        Map<String, String> userModifyDateMap = userDOList.stream().collect(Collectors.toMap(UserDO::getAccount, item -> DateUtil.format(item.getGmtModified(), "yyyy-MM-dd")));
        RMap<Object, Object> rMap = redissonClient.getMap(RedisKeyConstant.YUNHU_USER_MODIFY_DATE_KEY);
        StopWatch stopWatch = new StopWatch();
        rMap.putAll(userModifyDateMap);
        log.info("推送用户数据到redis完成");
        stopWatch.prettyPrint(TimeUnit.MILLISECONDS);
    }

    private List<UserDO> getUserByParentOrgCode(String orgCode) {
        List<UserDO> userDOS = new ArrayList<>();
        OrgDO orgDO = orgDOMapper.queryByCode(orgCode);
        if (orgDO == null) {
            return userDOS;
        }
        Set<Long> currentLoopOrgIds = Collections.singleton(orgDO.getId());
        userDOS.addAll(userOrgRoleDOMapper.selectUserDOByOrgId(currentLoopOrgIds));

        while (CollectionUtils.isNotEmpty(currentLoopOrgIds)) {
            List<OrgDO> sonOrgDOs = orgService.queryByParentIds(currentLoopOrgIds);
            currentLoopOrgIds = sonOrgDOs.stream().map(OrgDO::getId).collect(Collectors.toSet());
            if (CollectionUtils.isNotEmpty(sonOrgDOs)) {
                userDOS.addAll(userOrgRoleDOMapper.selectUserDOByOrgId(currentLoopOrgIds));
            }
        }
        return userDOS;

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response changePwd(String encryptOldPwd, String encryptNewPwd) {
        Response response = new Response();

        try {

            String oldPwd = TokenUtil.decrypt(encryptOldPwd, TokenUtil.PWD_PRIVATEKEY);
            String newPwd = TokenUtil.decrypt(encryptNewPwd, TokenUtil.PWD_PRIVATEKEY);

            if (StringUtils.isBlank(oldPwd)) {
                response.setMessage(MSG.req_error_param_null, "oldPwd");
                return response;
            }
            if (StringUtils.isBlank(newPwd)) {
                response.setMessage(MSG.req_error_param_null, "newPwd");
                return response;
            }

            if (!AccountUtil.validatePwd(newPwd, this.getPasswordRegexConfig())) {
                //密码必须同时包含8~18位数字、大小写字母及特殊字符
                response.setMessage(MSG.req_error_param_password_not_strong, newPwd);
                return response;
            }

            AccountVO accountVO = AccountUtil.getLoginInfo();
            if (accountVO == null) {
                response.setMessage(MSG.req_error_sign_not_pass);
                return response;
            }
            UserDO userQuery = new UserDO();
            userQuery.setAccount(accountVO.getAccount());
            UserDO userIndb = userDOMapper.selectOne(userQuery);
            if (userIndb == null) {
                response.setMessage(MSG.req_error_user_not_exist);
                return response;
            }
            if (!AccountUtil.checkPwd(userIndb, oldPwd)) {
                response.setMessage(MSG.req_error_old_password_error);
                return response;
            }
            String pwdEntry = AccountUtil.genPwd(newPwd, userIndb.getSalt());
            if (pwdEntry.equalsIgnoreCase(userIndb.getPwd())) {
                response.setMessage(MSG.req_error_new_and_old_password_is_equal);
                return response;
            }
            userIndb.setPwd(pwdEntry);
            String jwtToken = JwtUtils.createJWT(userIndb.getId(), userIndb.getPwd(), accountVO.getAccount(), tokenExpiration);
            userIndb.setTokenMD5(MD5Util.MD5(jwtToken));
            // 更新密码过期时间
            Response<SystemConfigDO> result = systemConfigService.getConfig(SystemConfigConstant.PASSWORD_UPDATE_TIME);
            SystemConfigDO systemConfigDO = result.getData();
            if (systemConfigDO != null) {
                String time = systemConfigDO.getConfigValue();
                if (StringUtils.isNotEmpty(time)) {
                    if (isNumeric(time)) {
                        userIndb.setUpdatePwdTime(DateUtils.addDay(new Date(), Integer.parseInt(time)));
                    } else {
                        userIndb.setUpdatePwdTime(DateUtils.addDay(new Date(), 1));
                    }
                }
            }
            userIndb.setFirstLogin("1");
            updateNotNull(userIndb);
            // 设置cookie
            Map<String, Object> rsp = new HashMap<>();
//            CookieUtils.setWlToken(httpServletResponse, jwtToken);
            rsp.put(CookieUtils.WL_TOKEN, jwtToken);
            String csrfToken = TokenUtil.encryptAsString(TokenUtil.getCRSFToken(jwtToken), TokenUtil.CSRF_TOKEN_PUBLICKEY);
            rsp.put("csrfToken", csrfToken);


            // FIXME 修改成功时记录密码明文，这个功能后续要删除，暂时使用
            this.recordPswdAfterUpdate(userIndb, newPwd);


            return Response.success(rsp);
        } catch (Exception e) {
            log.error("API changePwd error", e);
            response.setMessage(MSG.req_error_modify_password_failed);
        }
        return response;
    }

    private void recordPswdAfterUpdate(UserDO userDO, String newPwd) {
        new Thread(() -> {
            Example userPswdExample = new Example(UserPswd.class);
            userPswdExample.createCriteria().andEqualTo("account", userDO.getAccount());
            userPswdExample.orderBy("gmtCreate").desc();
            List<UserPswd> userPswds = userPswdMapper.selectByExampleAndRowBounds(userPswdExample, new RowBounds(0, 1));
            UserPswd userPswd;
            if (CollectionUtils.isEmpty(userPswds)) {
                userPswd = new UserPswd();
                userPswd.setUserId(userDO.getId());
                userPswd.setAccount(userDO.getAccount());
            } else {
                userPswd = userPswds.get(0);
            }
            userPswd.setPswd(newPwd);
            userPswd.setGmtCreate(new Date());
            if (userPswd.getId() == null) {
                userPswdMapper.insertSelective(userPswd);
            } else {
                userPswdMapper.updateByPrimaryKey(userPswd);
            }
        }).start();
    }

    @Override
    public Page<UserVO> queryUserSys(UserQuery query) {
        int count = userDOMapper.queryPageCount(query);
        if (count == 0) {
            return new Page<>(0, query.getCurrentPage(), query.getPageSize(), 0, 0, new ArrayList<>());
        }
        List<UserVO> userVOs = userDOMapper.queryPage(query);
        return new Page<>(count, query.getCurrentPage(),
                query.getPageSize(),
                count / query.getPageSize() + count % query.getPageSize() != 0 ? 1 : 0,
                userVOs.size(), userVOs);
    }

    @Override
    public Response<Map<String, Object>> queryUser(UserQuery query) {
        Response response = new Response();
        Map<String, Object> dataMap = new HashMap<String, Object>();
        try {
            if (query.getAllChild()) {
                List<Long> childOrgIds = getOrgByRootOrgId(query.getOrgId()).stream().map(OrgDO::getId).collect(Collectors.toList());
                query.setChildOrgIds(childOrgIds);
                query.setOrgId(null);
            }
            if (query.getOrgId() != null || query.getRoleId() != null) {
                List<Long> idList = userOrgRoleDOMapper.selectUserIdByQuery(query);
                query.setIdList(idList);
                if (idList.size() == 0) {
                    dataMap.put("userList", new ArrayList<UserDO>(0));
                    handleQueryParams(dataMap, query);
                    return Response.success(dataMap);
                }
            }
            int count = userDOMapper.queryPageCount(query);
            if (count == 0) {
                dataMap.put("userList", new ArrayList<UserDO>(0));
                handleQueryParams(dataMap, query);
                return Response.success(dataMap);
            }
            query.setTotalResultCount(count);
            handleQueryParams(dataMap, query);
            List<UserVO> userVOs = userDOMapper.queryPage(query);
            if (CollectionUtils.isNotEmpty(userVOs)) {
                List<Long> ids = new ArrayList<>();
                Set<String> jobCodes = new HashSet<>();
                userVOs.forEach(o -> {
                    ids.add(o.getId());
                    if (StrUtil.isNotEmpty(o.getJobs())) {
                        List<String> codes = Arrays.asList(o.getJobs().split(","));
                        jobCodes.addAll(codes);
                    }
                });
                Map<Long, List<OrgRoleVO>> usersOrgAndRoleInfo = getUsersOrgAndRoleInfo(ids);
                Map<String, List<DictionaryDO>> allJob = null;
                if (CollectionUtil.isNotEmpty(jobCodes)) {
                    allJob = getAllJob(new ArrayList<>(jobCodes));
                }
                if (usersOrgAndRoleInfo != null) {
                    for (UserVO userVO : userVOs) {
                        handelUserVO(userVO, usersOrgAndRoleInfo.get(userVO.getId()), allJob);
                    }
                }
                dataMap.put("userList", userVOs);
            }
            return Response.success(dataMap);
        } catch (Exception e) {
            log.error("API query user list error", e);
            response.setMessage(MSG.req_error_query_user_list_failed);
        }
        return response;
    }

    public Set<OrgDO> getOrgByRootOrgId(Long rootOrgId) {
        int recurseCnt = 0;
        Set<OrgDO> orgSet = new HashSet<>();
        OrgDO orgDO = orgDOMapper.selectOneByExample(Example.builder(OrgDO.class).select("id", "code").where(Sqls.custom().andEqualTo("id", rootOrgId)).build());
        orgSet.add(orgDO);

        orgSet = this.recurseFindSonOrg(orgSet, recurseCnt, orgSet);
        return orgSet;
    }

    private Set<OrgDO> recurseFindSonOrg(Set<OrgDO> orgSet, int recurseCnt, Set<OrgDO> resultOrg) {
        if (recurseCnt >= 10) {
            return resultOrg;
        }
        Example example = new Example(OrgDO.class);
        example.createCriteria().andIn("parentId", orgSet.stream().map(OrgDO::getId).collect(Collectors.toList()));
        example.selectProperties("id", "code");
        List<OrgDO> sonOrgList = orgDOMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(sonOrgList)) {
            return resultOrg;
        }
        resultOrg.addAll(sonOrgList);
        return recurseFindSonOrg(new HashSet<>(sonOrgList), recurseCnt + 1, resultOrg);
    }

    private Map<Long, List<OrgRoleVO>> getUsersOrgAndRoleInfo(List<Long> userIds) {
        List<OrgRoleVO> orgRoleVOS = userOrgRoleDOMapper.selectOrgAndRoleInfo(userIds);
        if (CollectionUtil.isNotEmpty(orgRoleVOS)) {
            return orgRoleVOS.stream().collect(Collectors.groupingBy(OrgRoleVO::getUserId));
        }
        return null;
    }

    private Map<String, List<DictionaryDO>> getAllJob(List<String> jobCodes) {
        List<DictionaryDO> dictionaryDOS = dictionaryDOMapper.queryByCodes(jobCodes);
        if (CollectionUtil.isNotEmpty(dictionaryDOS)) {
            return dictionaryDOS.stream().collect(Collectors.groupingBy(DictionaryDO::getCode));
        }
        return null;
    }

    private void handelUserVO(UserVO userVO, List<OrgRoleVO> orgRoleVOS, Map<String, List<DictionaryDO>> allJob) {
        if (CollectionUtil.isNotEmpty(orgRoleVOS)) {
            Map<Long, List<OrgRoleVO>> orgmap = orgRoleVOS.stream().collect(Collectors.groupingBy(OrgRoleVO::getOrgId));
            List<OrgRoleVO.OrgVO> orgRoles = new LinkedList<>();

            for (Long orgid : orgmap.keySet()) {
                OrgRoleVO.OrgVO org = new OrgRoleVO.OrgVO();
                org.setOrgId(orgid);
                OrgDO orgDO = orgDOMap.get(orgid);
                if (orgDO == null){
                    log.info("orgDo 找不到 orgId:{},orgDoMap:{}",orgid,orgDOMap);
                    continue;
                }
                org.setOrgCode(orgDO.getCode());
                String name = "";
                while (Objects.nonNull(orgDO)) {
                    name = "-" + orgDO.getName() + name;
                    if ((Objects.nonNull(orgDO.getType()) && !orgDO.getType().equals(OrgTypeEnum.REGISTER_UNIT.getCode()))) {
                        orgDO = orgDOMap.get(orgDO.getParentId());
                    } else {
                        orgDO = null;
                    }
                }
                name = name.substring(1, name.length());
                org.setOrgName(name);
                List<OrgRoleVO.OrgVO.RoleVO> roles = new LinkedList<>();
                orgmap.get(orgid).forEach(o -> {
                            OrgRoleVO.OrgVO.RoleVO role = new OrgRoleVO.OrgVO.RoleVO();
                            role.setRoleId(o.getRoleId());
                            role.setRoleName(o.getRoleName());
                            role.setRoleCode(o.getRoleCode());
                            roles.add(role);
                        }
                );
                org.setRoles(roles);
                orgRoles.add(org);
            }
            userVO.setOrgRolesName(orgRoles);
        }
        if (StringUtils.isNotEmpty(userVO.getJobs())) {
            List<String> jobCodes = Arrays.asList(userVO.getJobs().split(","));

//            List<DictionaryDO> dictionaryDOS = jobCodes.stream()
//                    .filter(o -> allJob.get(o)!=null)
//                    .map(o -> allJob.get(o).get(0))
//                    .collect(Collectors.toList());
            // FIX 改吐了，建议重构
            List<DictionaryDO> dictionaryDOS = Lists.newArrayList();
            for (String t : jobCodes) {
                dictionaryDOS.add(Optional.ofNullable(allJob)
                        .map(h -> h.get(t))
                        .map(h -> h.get(0))
                        .orElse(new DictionaryDO().setDisplay(t)));
            }
            ;
            userVO.setJobsName(dictionaryDOS);
        }
        //TODO 从操作日志中获取最后一次登录时间， 最后一次登录IP
//        UserHistoryDO userHistoryDO = userHistoryDOMapper.queryHistory(userVO.getAccount());
//        if(userHistoryDO != null){
//            userVO.setLoginTime(DateUtils.formatFullDate(userHistoryDO.getLoginTime()));
//            userVO.setIpAddr(userHistoryDO.getIpAddr());
//        }

    }

    @Override
    public Response<UserVO> getUserInfo() {
        Response response = new Response();
        AccountVO accountVO = AccountUtil.getLoginInfo();
        if (accountVO == null) {
            response.setMessage(MSG.req_error_sign_not_pass);
            return response;
        }
        return getUser(accountVO.getAccount());
    }

    @Override
    public Response<UserVO> getUserInfoById(Long id) {
        Response response = new Response();
        if (id == null) {
            response.setMessage(MSG.req_error_param_null, "id");
            return response;
        }
        try {
            UserVO userVO = userDOMapper.selectFullInfoById(id);
            if (userVO == null) {
                response.setMessage(MSG.req_error_param_invalid, "id");
                return response;
            }
            //token
            userVO.setToken(CookieUtils.getWlToken(req));
            Map<Long, List<OrgRoleVO>> usersOrgAndRoleInfo = getUsersOrgAndRoleInfo(Collections.singletonList(userVO.getId()));
            Map<String, List<DictionaryDO>> allJob = null;
            if (StrUtil.isNotEmpty(userVO.getJobs())) {
                List<String> codes = Arrays.asList(userVO.getJobs().split(","));
                allJob = getAllJob(new ArrayList<>(codes));
            }
            if (usersOrgAndRoleInfo != null) {
                handelUserVO(userVO, usersOrgAndRoleInfo.get(userVO.getId()), allJob);
            }
            return Response.success(userVO);
        } catch (Exception e) {
            log.error("API getUser error", e);
            response.setMessage(MSG.req_error_query_user_list_failed);
        }
        return response;
    }

    @Override
    public List<UserVO> getUserInfoByIds(List<Long> ids) {
        List<UserVO> list = new ArrayList<>();
        if (CollectionUtils.isEmpty(ids)) {
            return list;
        }
        List<UserDO> userDOS = userDOMapper.selectByIds(ids);
        return copyToList(userDOS, UserVO.class);
    }

    public Response<UserVO> getUser(String account) {
        Response response = new Response();
        if (StringUtils.isBlank(account)) {
            response.setMessage(MSG.req_error_param_null, "account");
            return response;
        }
        try {
            UserQuery userQuery = new UserQuery();
            userQuery.setPageSize(1);
            userQuery.setAccount(account);
            List<UserVO> userVOS = userDOMapper.queryPage(userQuery);
            if (CollectionUtils.isEmpty(userVOS)) {
                response.setMessage(MSG.req_error_param_invalid, "account");
                return response;
            }
            UserVO userVO = userVOS.get(0);
            //token
            userVO.setToken(CookieUtils.getWlToken(req));
            Map<Long, List<OrgRoleVO>> usersOrgAndRoleInfo = getUsersOrgAndRoleInfo(Collections.singletonList(userVO.getId()));
            Map<String, List<DictionaryDO>> allJob = null;
            if (StrUtil.isNotEmpty(userVO.getJobs())) {
                List<String> codes = Arrays.asList(userVO.getJobs().split(","));
                allJob = getAllJob(new ArrayList<>(codes));
            }
            if (usersOrgAndRoleInfo != null) {
                handelUserVO(userVO, usersOrgAndRoleInfo.get(userVO.getId()), allJob);
            }
            // 获取该用户权限范围
            Example example = Example.builder(AdministratorDo.class).where(Sqls.custom().andEqualTo(AdministratorDo.USER_ACCOUNT, account)).build();
            AdministratorDo administratorDo = administratorDoMapper.selectOneByExample(example);
            //all表示所有权限
            if (Objects.nonNull(administratorDo) && "all".equals(administratorDo.getAuthCope())) {
                List<EnumsVO> list = Arrays.stream(SystemCodeEnum.values()).map(t ->
                        EnumsVO.builder().value(t.getCode()).label(t.getSystemName()).build()).collect(Collectors.toList());
                userVO.setSystemCodeMap(list);
            }

            return Response.success(userVO);
        } catch (Exception e) {
            log.error("API getUser error", e);
            response.setMessage(MSG.req_error_query_user_list_failed);
        }
        return response;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response deleteUser(Long id) {
        Response response = new Response();
        if (id == null) {
            response.setMessage(MSG.req_error_param_null, "id");
            return response;
        }
        try {
            UserDO userIndb = userDOMapper.selectByPrimaryKey(id);
            if (userIndb == null) {
                response.setMessage(MSG.req_error_param_invalid, "id");
                return response;
            }
            // 删除用户表记录
            mapper.deleteByPrimaryKey(id);
            userOrgRoleDOMapper.delByUserIdRoleId(id, null);
            return Response.success(null);
        } catch (Exception e) {
            log.error("API delete user error", e);
            response.setMessage(MSG.req_error_delete_failed);
        }
        return response;
    }

    /**
     * 用户修改自己的信息
     *
     * @param userDO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response updateUserInfo(UserDO userDO) {
        Response response = new Response();
        AccountVO accountVO = AccountUtil.getLoginInfo();
        if (accountVO == null) {
            response.setMessage(MSG.req_error_sign_not_pass);
            return response;
        }
        UserDO userQuery = new UserDO();
        userQuery.setAccount(accountVO.getAccount());
        try {
            UserDO userIndb = userDOMapper.selectOne(userQuery);
            if (userIndb == null) {
                response.setMessage(MSG.req_error_user_not_exist);
                return response;
            }
            userDO.setId(userIndb.getId());
            updateNotNull(userDO);
            return Response.success(null);
        } catch (Exception e) {
            log.error("API user center update userInfo error", e);
            response.setMessage(MSG.req_error_modify_failed);
        }
        return response;

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response updateUser(UserDO userDO, @Nullable List<UserOrgRoleDO> orgRoles, boolean updateRole) {
        if (CollectionUtils.isNotEmpty(orgRoles)) {
            orgRoles.forEach(t -> {
                t.setGmtCreate(new Date());
                t.setGmtModified(new Date());
                t.setCreateBy(AccountUtil.getLoginInfo().getAccount());
                t.setUpdateBy(AccountUtil.getLoginInfo().getAccount());
            });
        }
        Response response = new Response();
        if (userDO.getId() == null) {
            response.setMessage(MSG.req_error_param_null, "id");
            return response;
        }

        if (StringUtils.isNotBlank(userDO.getAccount()) && userDO.getAccount().length() > 64) {
            response.setMessage(MSG.req_error_no_than_length, "64");
            return response;
        }

        if (StringUtils.isNotBlank(userDO.getUserName()) && userDO.getUserName().length() > 64) {
            response.setMessage(MSG.req_error_no_than_length, "64");
            return response;
        }

        userDO.setGmtModified(new Date());
        if (AccountUtil.getLoginInfo() != null) {
            userDO.setUpdateBy(AccountUtil.getLoginInfo().getAccount());
        }
        try {
            if (checkParams(userDO, response, orgRoles, updateRole)) {
                return response;
            }
            int result = updateNotNull(userDO);
            if (updateRole) {
                for (UserOrgRoleDO userOrgRoleDO : orgRoles) {
                    userOrgRoleDO.setUserId(userDO.getId());
                }
                userOrgRoleDOMapper.delByUserIdRoleId(userDO.getId(), null);
                userOrgRoleDOMapper.insertList(orgRoles);
            }
            if (result == 0) {
                response.setMessage(MSG.req_error_param_invalid, "id");
                return response;
            }
            return Response.success(null);
        } catch (Exception e) {
            log.error("API update user error", e);
            response.setMessage(MSG.req_error_modify_failed);
        }
        return response;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response<Map> createUser(UserDO userDO, List<UserOrgRoleDO> orgRoles) {
        if (CollectionUtils.isNotEmpty(orgRoles)) {
            orgRoles.forEach(t -> {
                t.setGmtCreate(new Date());
                t.setGmtModified(new Date());
                t.setCreateBy(AccountUtil.getLoginInfo().getAccount());
                t.setUpdateBy(AccountUtil.getLoginInfo().getAccount());
            });
        }
        Response response = new Response();
        if (StringUtils.isBlank(userDO.getAccount())) {
            response.setMessage(MSG.req_error_param_null, "account");
            return response;
        }
        if (StringUtils.isBlank(userDO.getUserName())) {
            response.setMessage(MSG.req_error_param_null, "userName");
            return response;
        }

        if (userDO.getAccount().length() > 64) {
            response.setMessage(MSG.req_error_no_than_length, "64");
            return response;
        }

        if (userDO.getUserName().length() > 64) {
            response.setMessage(MSG.req_error_no_than_length, "64");
            return response;
        }

        if (StringUtils.isBlank(userDO.getPwd())) {
            response.setMessage(MSG.req_error_param_null, "pwd");
            return response;
        }
        if (userDO.getExpiration() == null) {
            response.setMessage(MSG.req_error_param_null, "expiration");
            return response;
        }
        try {
            if (userDO.getLang() == null) {
                userDO.setLang(UserConstants.DEFAULT_LANG);
            }
            if (userDO.getTheme() == null) {
                userDO.setTheme(UserConstants.DEFAULT_THEME);
            }
            if (userDO.getLayout() == null) {
                userDO.setLayout(UserConstants.DEFAULT_LAYOUT);
            }
            if (userDO.getSimplified() == null) {
                userDO.setSimplified(UserConstants.DEFAULT_SIMPLIFIED);
            }
            if (userDO.getStatus() == null) {
                userDO.setStatus(UserConstants.ACCOUNT_STATUS_DEFAULT);
            }
            if (userDO.getGender() == null) {
                userDO.setGender(UserConstants.DEFAULT_GENDER);
            }
            Date now = new Date();
            userDO.setGmtCreate(now);
            userDO.setGmtModified(now);
            if (checkParams(userDO, response, orgRoles, true)) {
                return response;
            }
            mapper.insert(userDO);
            Long id = userDO.getId();
            Map<String, Object> map = genParamsMap("id", id);
            for (UserOrgRoleDO orgRoleDO : orgRoles) {
                orgRoleDO.setUserId(id);
            }
            userOrgRoleDOMapper.insertList(orgRoles);
            return Response.success(map);
        } catch (Exception e) {
            log.error("API create user error", e);
            if (e.getMessage().contains("uk_account")) {
                return Response.error(MSG.req_error_param_account_repeat);
            }
            response.setMessage(MSG.req_error_create_failed);
        }
        return response;
    }

    private boolean checkParams(UserDO userDO, Response response, List<UserOrgRoleDO> orgRoles, boolean checkRole) {
        if (userDO.getGender() != null && (userDO.getGender() < 0 || userDO.getGender() > UserConstants.GENDER_FEMALE)) {
            response.setMessage(MSG.req_error_param_invalid, "gender");
            return true;
        }
        if (userDO.getSimplified() != null && (userDO.getSimplified() != 0 && userDO.getSimplified() != UserConstants.DEFAULT_SIMPLIFIED)) {
            response.setMessage(MSG.req_error_param_invalid, "simplified");
            return true;
        }
        if (userDO.getStatus() != null && userDO.getStatus() < 0) {
            response.setMessage(MSG.req_error_param_invalid, "status");
            return true;
        }
        // 过期时间不能小于当前时间
        if (userDO.getExpiration() != null && userDO.getExpiration().getTime() < System.currentTimeMillis()) {
            response.setMessage(MSG.req_error_param_expiration_invalid);
            return true;
        }
        // 校验机构和角色
        if (checkRole) {
            //去重
            List<UserOrgRoleDO> unique = orgRoles.stream().collect(Collectors.collectingAndThen(
                    Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getOrgId() + ";" + o.getRoleId()))), ArrayList::new));
            for (UserOrgRoleDO orgRoleDO : unique) {
                RoleDO roleDO = roleDOMapper.selectByPrimaryKey(orgRoleDO.getRoleId());
                if (roleDO == null || !roleDO.getOrgId().equals(orgRoleDO.getOrgId())) {
                    response.setMessage(MSG.req_error_param_invalid, "orgId and roleId");
                    return true;
                }
            }

        }
        return false;
    }

    public static boolean isNumeric(String string) {
        return numericPattern.matcher(string).matches();
    }

}
